package moviescraper.doctord.view;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

import org.apache.commons.lang3.StringUtils;

import moviescraper.doctord.controller.amalgamation.AllAmalgamationOrderingPreferences;
import moviescraper.doctord.controller.amalgamation.DataItemSourceAmalgamationPreference;
import moviescraper.doctord.controller.amalgamation.MovieScrapeResultGroup;
import moviescraper.doctord.controller.amalgamation.ScrapeAmalgamatedMovieWorker;
import moviescraper.doctord.controller.amalgamation.ScrapeAmalgamatedMovieWorker.ScrapeAmalgamatedMovieWorkerProperty;
import moviescraper.doctord.controller.amalgamation.ScraperGroupAmalgamationPreference;
import moviescraper.doctord.controller.siteparsingprofile.SiteParsingProfile;
import moviescraper.doctord.controller.siteparsingprofile.SiteParsingProfile.ScraperGroupName;
import moviescraper.doctord.controller.siteparsingprofile.specific.Data18MovieParsingProfile;
import moviescraper.doctord.controller.siteparsingprofile.specific.TheMovieDatabaseParsingProfile;
import moviescraper.doctord.model.Movie;
import moviescraper.doctord.model.SearchResult;
import moviescraper.doctord.model.dataitem.DataItemSource;
import moviescraper.doctord.model.dataitem.Thumb;
import moviescraper.doctord.model.preferences.MoviescraperPreferences;
import moviescraper.doctord.view.customcomponents.AsyncImageComponent;

public class ScrapeAmalgamatedProgressDialog extends JDialog implements Runnable {

	private static final long serialVersionUID = 1L;

	//FileDetailPanel fileDetailPanel;
	private List<Movie> currentMovieList;

	private Movie currentAmalgamatedMovie;
	//ScraperGroupAmalgamationPreference amalgamationPreferences;
	private GUIMain guiMain;
	private JButton cancelButton;
	private JProgressBar progressBar;
	private ScrapeAmalgamatedMovieWorker worker;
	int currentFileIndexToScrape = 0;
	private static final int maxLettersToDisplayOfFileName = 80;
	private AmalgamationPropertyChangeListener propertyListener;
	private ScraperGroupAmalgamationPreference scraperGroupAmalgamationPreference;
	private List<ScraperProgressView> scraperProgressViews;
	AllAmalgamationOrderingPreferences allAmalgamationOrderingPreferences;

	private List<File> filesWeAreScraping;
	private JLabel fileBeingScrapedLabel;

	private boolean showPosterPicker;
	private boolean showFanartPicker;

	//private final PropertyChangeSupport propertyChangeSupport;

	public static void main(String[] args) {
		DataItemSourceAmalgamationPreference overallOrdering = new DataItemSourceAmalgamationPreference(new TheMovieDatabaseParsingProfile(), new Data18MovieParsingProfile());
		ScraperGroupAmalgamationPreference amalgamationPreferences = new ScraperGroupAmalgamationPreference(ScraperGroupName.AMERICAN_ADULT_DVD_SCRAPER_GROUP, overallOrdering);
		GUIMain guiMain = new GUIMain();
		ScrapeAmalgamatedProgressDialog action = new ScrapeAmalgamatedProgressDialog(guiMain, guiMain.getAllAmalgamationOrderingPreferences(), amalgamationPreferences);
		// schedule this for the event dispatch thread (edt)
		SwingUtilities.invokeLater(action);
	}

	public void setFilesBeingScrapedLabel() {
		if (fileBeingScrapedLabel == null)
			fileBeingScrapedLabel = new JLabel();

		if (filesWeAreScraping != null && filesWeAreScraping.size() > 0) {
			File currentFile = filesWeAreScraping.get(currentFileIndexToScrape);
			if (currentFile != null) {
				String truncatedName = StringUtils.abbreviate(currentFile.getName(), maxLettersToDisplayOfFileName);
				fileBeingScrapedLabel.setText("<html>Scraping: <b>" + truncatedName + "</b> (" + (currentFileIndexToScrape + 1) + "/" + filesWeAreScraping.size() + ")" + "</html>");
			}
		} else {
			fileBeingScrapedLabel.setText("No files selected.");
		}
	}

	public void cancelRunningScraper(SiteParsingProfile scraper) {
		if (worker != null) {
			worker.cancelRunningScraper(scraper);
		}
	}

	public void reinitializeScrapingForNextMovie() {
		currentAmalgamatedMovie = null;
		currentMovieList = new LinkedList<>();
		for (ScraperProgressView currentScraperProgressView : scraperProgressViews) {
			currentScraperProgressView.resetPanelForNextScrape();
		}
		setFilesBeingScrapedLabel();
		progressBar.setValue(0);
	}

	public ScrapeAmalgamatedProgressDialog(GUIMain guiMain, AllAmalgamationOrderingPreferences allAmalgamationOrderingPreferences,
	        ScraperGroupAmalgamationPreference scraperGroupAmalgamationPreference) {
		super(guiMain.getFrmMoviescraper());
		setTitle("Scraping...");
		this.guiMain = guiMain;
		this.filesWeAreScraping = guiMain.getCurrentlySelectedMovieFileList();
		this.allAmalgamationOrderingPreferences = allAmalgamationOrderingPreferences;
		scraperProgressViews = new LinkedList<>();

		showPosterPicker = MoviescraperPreferences.getInstance().getSelectArtManuallyWhenScraping();
		showFanartPicker = MoviescraperPreferences.getInstance().getSelectArtManuallyWhenScraping();

		this.scraperGroupAmalgamationPreference = scraperGroupAmalgamationPreference;
		currentMovieList = new LinkedList<>();
		propertyListener = new AmalgamationPropertyChangeListener();

		JPanel overallPanel = new JPanel(new BorderLayout());
		this.setLocationRelativeTo(guiMain.getFrmMoviescraper());
		this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		this.add(overallPanel);
		progressBar = new JProgressBar(0, 100);
		progressBar.setValue(0);
		progressBar.setStringPainted(true);
		cancelButton = new JButton("Cancel");
		cancelButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				if (worker != null) {
					worker.cancelAllRunningScrapers();
					if (ScrapeAmalgamatedProgressDialog.this.guiMain != null) {
						ScrapeAmalgamatedProgressDialog.this.guiMain.setMainGUIEnabled(true);
					}
					ScrapeAmalgamatedProgressDialog.this.dispose();
				}

			}
		});

		fileBeingScrapedLabel = new JLabel();
		setFilesBeingScrapedLabel();
		overallPanel.add(fileBeingScrapedLabel, BorderLayout.NORTH);
		JPanel southPanel = new JPanel(new BorderLayout());
		southPanel.add(cancelButton, BorderLayout.SOUTH);
		southPanel.add(progressBar, BorderLayout.CENTER);
		overallPanel.add(southPanel, BorderLayout.SOUTH);
		this.setVisible(true);
		if (filesAreSelected()) {
			//start scraping the first item in the selected file list, further items will be scraped once this one completes and a "AllScrapesFinished" message is received
			scrapeWithIndex(currentFileIndexToScrape);

		} else {
			System.err.println("No file selected!");
		}

		JPanel individualScrapeProgressPanel = createScraperProgressPanel();
		overallPanel.add(individualScrapeProgressPanel, BorderLayout.CENTER);
		this.pack();
	}

	private JPanel createScraperProgressPanel() {
		JPanel scraperProgressPanel = new JPanel();
		scraperProgressPanel.setLayout(new BoxLayout(scraperProgressPanel, BoxLayout.Y_AXIS));
		List<DataItemSource> activeScrapers = scraperGroupAmalgamationPreference.getActiveScrapersUsedInOverallPreference();
		//update the state of what's active because it may have changed from elsewhere

		for (DataItemSource currentScraper : activeScrapers) {
			if (currentScraper != null && currentScraper instanceof SiteParsingProfile && shouldScrapeThread(currentScraper)) {
				ScraperProgressView currentProgressView = new ScraperProgressView(currentScraper, this);
				scraperProgressPanel.add(currentProgressView);
				this.addPropertyChangeListener(currentProgressView.getScraperProgressPropertyChangeListener());
				scraperProgressViews.add(currentProgressView);
			}
		}
		return scraperProgressPanel;
	}

	private boolean shouldScrapeThread(DataItemSource parsingProfile) {
		//Default group used for single scraper operations
		if (scraperGroupAmalgamationPreference.getScraperGroupName().equals(ScraperGroupName.DEFAULT_SCRAPER_GROUP)) {
			return true;
		}
		for (ScraperGroupName currentName : ScraperGroupName.values()) {
			ScraperGroupAmalgamationPreference currentPref = allAmalgamationOrderingPreferences.getScraperGroupAmalgamationPreference(currentName);

			LinkedList<DataItemSource> overallPrefs = currentPref.getOverallAmalgamationPreference().getAmalgamationPreferenceOrder();

			for (DataItemSource currentDataItemSource : overallPrefs) {
				if (currentDataItemSource.getDataItemSourceName().equals(parsingProfile.getDataItemSourceName())) {
					boolean disabled = currentDataItemSource.isDisabled();
					return !disabled;
				}
			}
		}
		return false;
	}

	public void scrapeWithIndex(int index) {
		//TODO: I may not need to do all this reconstruction and removing listener stuff
		//i added this in to fix a bug, but I think this was unrelated and can be cleaned up
		//it would require some pretty extensive testing though, so i'm leaving this in to be safe for now
		if (worker != null) {
			worker.cancel(true);
			if (propertyListener != null)
				worker.removePropertyChangeListener(propertyListener);
		}
		worker = new ScrapeAmalgamatedMovieWorker(allAmalgamationOrderingPreferences, scraperGroupAmalgamationPreference, guiMain.getCurrentlySelectedMovieFileList().get(currentFileIndexToScrape),
		        this);
		propertyListener = new AmalgamationPropertyChangeListener();
		worker.addPropertyChangeListener(propertyListener);
		worker.execute();
	}

	/**
	 * Scrapes the next item (returns true) or returns false if there is no next item to scrape
	 * 
	 * @return true if we scraped an item, false if we didn't
	 */
	public boolean scrapeNextItemIfNeeded() {

		boolean considerUserSelectionOneURLWhenScraping = MoviescraperPreferences.getInstance().getConsiderUserSelectionOneURLWhenScraping();
		if (considerUserSelectionOneURLWhenScraping) {
			for (int i = 1; i < guiMain.getCurrentlySelectedMovieFileList().size(); i++) {
				if (currentAmalgamatedMovie != null) {
					guiMain.movieToWriteToDiskList.add(currentAmalgamatedMovie);
				}
			}
			currentFileIndexToScrape = guiMain.getCurrentlySelectedMovieFileList().size();
		}

		if (filesAreSelected() && (currentFileIndexToScrape + 1) < guiMain.getCurrentlySelectedMovieFileList().size()) {
			currentFileIndexToScrape++;
			reinitializeScrapingForNextMovie();
			scrapeWithIndex(currentFileIndexToScrape);
			return true;
		}
		return false;
	}

	public boolean filesAreSelected() {
		if (guiMain != null && guiMain.getCurrentlySelectedMovieFileList() != null && guiMain.getCurrentlySelectedMovieFileList().size() > 0)
			return true;
		else
			return false;
	}

	@Override
	public void run() {
	}

	private class AmalgamationPropertyChangeListener implements PropertyChangeListener {

		@Override
		public void propertyChange(PropertyChangeEvent evt) {

			firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());

			if (evt.getPropertyName().equals("progress")) {
				int progressAmount = (int) evt.getNewValue();
				progressBar.setValue(progressAmount);
			}
			if (evt.getPropertyName().equals(ScrapeAmalgamatedMovieWorkerProperty.ALL_SCRAPES_FINISHED.toString())) {
				if (currentAmalgamatedMovie == null) {
					guiMain.movieToWriteToDiskList.add(currentAmalgamatedMovie);
				} else if (guiMain != null) {
					promptUserToPickPoster();
					promptUserToPickFanart();

					guiMain.movieToWriteToDiskList.add(currentAmalgamatedMovie);
					guiMain.getFileDetailPanel().setNewMovie(currentAmalgamatedMovie, true, false);
				}
				//scrape the next selected item
				boolean weAreDone = !scrapeNextItemIfNeeded();
				if (weAreDone) {
					//renable the main gui and close our scraping dialog
					if (guiMain != null) {
						guiMain.getFrmMoviescraper().setCursor(Cursor.getDefaultCursor());
						ScrapeAmalgamatedProgressDialog.this.guiMain.setMainGUIEnabled(true);
						ScrapeAmalgamatedProgressDialog.this.dispose();
					}
				}
			}
			if (evt.getPropertyName().equals(ScrapeAmalgamatedMovieWorkerProperty.SCRAPED_MOVIE.toString())) {
				@SuppressWarnings("unchecked")
				List<Map<SiteParsingProfile, Movie>> newValue = (List<Map<SiteParsingProfile, Movie>>) evt.getNewValue();
				newValue.removeAll(Collections.singleton(null));
				for (Map<SiteParsingProfile, Movie> currentMap : newValue) {
					List<Movie> currentMovies = new ArrayList<>(currentMap.values());
					currentMovies.removeAll(Collections.singleton(null));
					currentMovieList.addAll(currentMovies);
				}
				updateAmalgamatedMovie();
			}
		}

	}

	public void updateAmalgamatedMovie() {
		if (currentMovieList != null && currentMovieList.size() > 0) {
			//we construct a new one instead of using our local variable to get around bug where changes to amalgamation settings were not getting picked up until program restart
			ScraperGroupAmalgamationPreference prefToUse = guiMain.getAllAmalgamationOrderingPreferences()
			        .getScraperGroupAmalgamationPreference(scraperGroupAmalgamationPreference.getScraperGroupName());
			MovieScrapeResultGroup scrapedResultGroup = new MovieScrapeResultGroup(currentMovieList, prefToUse);

			currentAmalgamatedMovie = scrapedResultGroup.amalgamateMovie();
		}

	}

	/**
	 * Displays a dialog where the user can pick the poster to use. Only shows when there is more than one poster.
	 */
	private void promptUserToPickPoster() {
		if (showPosterPicker && currentAmalgamatedMovie != null && currentAmalgamatedMovie.getPosters() != null && currentAmalgamatedMovie.getPosters().length > 1) {
			Thumb posterFromUserSelection = showArtPicker(this, currentAmalgamatedMovie.getPosters(), "Pick Poster", true);
			currentAmalgamatedMovie.moveExistingPosterToFront(posterFromUserSelection);
		}
	}

	/**
	 * Displays a dialog where the user can pick the fanart to use. Only shows when there is more than one fanart.
	 */
	private void promptUserToPickFanart() {
		if (showFanartPicker && currentAmalgamatedMovie != null && currentAmalgamatedMovie.getFanart() != null && currentAmalgamatedMovie.getFanart().length > 1) {
			Thumb fanartFromUserSelection = showArtPicker(this, currentAmalgamatedMovie.getFanart(), "Pick Fanart", false);
			currentAmalgamatedMovie.moveExistingFanartToFront(fanartFromUserSelection);
		}
	}

	public static Thumb showArtPicker(Component parent, Thumb[] thumbArray, String windowTitle, boolean isForPoster) {
		if (thumbArray.length > 0) {
			JPanel panel = new JPanel();
			panel.setLayout(new BorderLayout());
			JPanel thumbPane = new JPanel(new ModifiedFlowLayout());
			AsyncImageComponent[] thumbPanels = new AsyncImageComponent[thumbArray.length];
			boolean doAutoSelect = true;
			for (int i = 0; i < thumbArray.length; i++) {

				thumbPanels[i] = new AsyncImageComponent(thumbArray[i], false, thumbPanels, doAutoSelect, isForPoster, true);
				thumbPane.add(thumbPanels[i]);
			}
			JScrollPane thumbScroller = new JScrollPane(thumbPane);
			panel.add(thumbScroller, BorderLayout.CENTER);
			panel.setPreferredSize(new Dimension(325, 600));

			final JDialog bwin = new JDialog();
			bwin.addWindowFocusListener(new WindowFocusListener() {
				@Override
				public void windowLostFocus(WindowEvent e) {
					bwin.setVisible(false);
					bwin.dispose();
				}

				@Override
				public void windowGainedFocus(WindowEvent e) {
				}
			});
			bwin.add(panel);
			bwin.pack();

			int result = JOptionPane.showOptionDialog(parent, panel, windowTitle, JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null, null);
			if (result == JOptionPane.OK_OPTION) {
				//get the selected item's thumb
				Thumb optionPickedFromPanel = null;
				for (int i = 0; i < thumbPanels.length; i++) {
					if (thumbPanels[i].isSelected()) {
						optionPickedFromPanel = thumbPanels[i].getThumb();
						System.out.println("returning option picked from panel = " + optionPickedFromPanel);
						return optionPickedFromPanel;
					}
				}

			} else
				return null;
		}
		return null;
	}

	public boolean showPromptForUserProvidedURL(SiteParsingProfile siteScraper, File fileToScrape) {
		boolean promptUserForURLWhenScraping = MoviescraperPreferences.getInstance().getPromptForUserProvidedURLWhenScraping();
		boolean chooseSearchResult = MoviescraperPreferences.getInstance().getSelectSearchResultManuallyWhenScraping();
		boolean wasCustomURLSet = false;
		if (promptUserForURLWhenScraping) {
			System.out.println("Prompting the user for a url for " + siteScraper);
			String userProvidedURL = (String) JOptionPane.showInputDialog(null, "Enter URL of " + siteScraper.toString() + " to scrape from:", "Scrape from this URL...", JOptionPane.PLAIN_MESSAGE,
			        null, null, null);

			if (userProvidedURL != null && userProvidedURL.length() > 0) {
				//TODO: validate this is a actually a URL and display an error message if it is not
				//also maybe don't let them click OK if isn't a valid URL?
				try {
					URL isAValidURL = new URL(userProvidedURL);
					siteScraper.setOverridenSearchResult(isAValidURL.toString());
					wasCustomURLSet = true;
				} catch (MalformedURLException e) {
					e.printStackTrace();
				}

			}
		}
		//if the user doesn't choose their URL manually and they have the preference set to allow picking of search results,
		//give the user the ability to pick one
		if (chooseSearchResult && !wasCustomURLSet) {
			System.out.println("Prompt user for search results for " + siteScraper);
			String searchString = siteScraper.createSearchString(fileToScrape);
			try {
				SearchResult[] searchResults = siteScraper.getSearchResults(searchString);
				if (searchResults != null && searchResults.length > 0) {
					SearchResult searchResultFromUser = this.showSearchResultPicker(searchResults, siteScraper.getDataItemSourceName());
					if (searchResultFromUser != null) {
						siteScraper.setOverridenSearchResult(searchResultFromUser.getUrlPath());
						wasCustomURLSet = true;
					} else {
						//User hit cancel, do not scrape from this scraper
						System.out.println("Discarding results from " + siteScraper.getDataItemSourceName());
						siteScraper.setDiscardResults(true);
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
		return wasCustomURLSet;
	}

	/**
	 * Display a list of option dialog where the user can choose between various search results
	 * 
	 * @param searchResults - search results to display
	 * @param siteName - Title to show in window
	 * @return - the search result the user picks or null if the user picks the cancel option
	 */
	public SearchResult showSearchResultPicker(SearchResult[] searchResults, String siteName) {
		if (searchResults.length > 0) {

			SelectionDialog selectionDialog = new SelectionDialog(searchResults, siteName);
			Object[] choices = { "OK", "Skip Scraping From This Site" };
			Object defaultChoice = choices[0];
			int optionPicked = JOptionPane.showOptionDialog(this, selectionDialog, "Select Search Result for " + siteName, JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, choices,
			        defaultChoice);
			if (optionPicked == JOptionPane.CANCEL_OPTION)
				return null;
			return selectionDialog.getSelectedValue();
		} else
			return null;
	}
}
